package siena.dbslayer;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Properties;

import siena.PersistenceManager;
import siena.SienaException;
import siena.logging.SienaLogger;
import siena.logging.SienaLoggerFactory;

/**
 * This class represents the abstract class for the Persistence Manager.
 * 
 * @author jsanca
 * 
 */
public abstract class AbstractRemotePersistenceManager implements
		PersistenceManager {

	private Connector connector = null;

	private Serializer serializerInput = null;

	private Serializer serializerOutput = null;

	private boolean useMethodTunnelingByDefault = false;

	protected SienaLogger logger = SienaLoggerFactory
			.getLogger(AbstractRemotePersistenceManager.class);

	public AbstractRemotePersistenceManager(Properties properties) {

		String connectorImpl = properties.getProperty("connector");
		String serializerImplIN = (null == properties
				.getProperty("serializer.input")) ? properties
				.getProperty("serializer") : properties
				.getProperty("serializer.input");
		String serializerImplOUT = (null == properties
				.getProperty("serializer.output")) ? properties
				.getProperty("serializer") : properties
				.getProperty("serializer.output");

		this.connector = Connector.class.cast(ReflectionUtil.getInstance(
				connectorImpl, URLConnector.class));
		this.connector.configure(properties);

		this.serializerInput = Serializer.class.cast(ReflectionUtil
				.getInstance(serializerImplIN, JsonSerializer.class));
		// If the user didn't set the output serializer
		this.serializerOutput = (null == properties
				.getProperty("serializer.output")) ? Serializer.class
				.cast(ReflectionUtil.getInstance(serializerImplOUT,
						JsonSerializer.class)) : this.serializerInput;
	} // AbstractRemotePersistenceManager.

	public void setConnector(Connector connector) {
		this.connector = connector;
	} // setConnector.

	public void setSerializerInput(Serializer serializerInput) {
		this.serializerInput = serializerInput;
	}

	public void setSerializerOutput(Serializer serializerOutput) {
		this.serializerOutput = serializerOutput;
	}

	/**
	 * Default method to send the data and receive the response, usually this
	 * method send via "POST"
	 * 
	 * @param request
	 *            Object Request
	 * @return Object response
	 */
	protected Object send(Object request) {

		return this.sendPost(request);
	} // send.

	/**
	 * This is the main method to send and receive streams to some network
	 * place.
	 * 
	 * @param request
	 *            Object object to send
	 * @param serializerIN
	 *            {@link Serializer} serializer to convert the request object in
	 *            the stream
	 * @param serializerOUT
	 *            {@link Serializer} serializer to convert the response stream
	 *            in object
	 * @param method
	 *            {@link RequestMethod} method used to send by default POST
	 * @param useMethodTunneling
	 *            boolean if it is true, send the message by POST overriding the
	 *            method.
	 * @return Object response.
	 */
	protected Object sendMessageWithBody(Object request,
			Serializer serializerIN, Serializer serializerOUT,
			RequestMethod method, boolean useMethodTunneling) {

		Object response = null;

		try {

			this.connector.connect(method, useMethodTunneling);
			serializerIN.serialize(request, this.connector.getOutputStream());
			response = serializerOUT.deserialize(this.connector
					.getInputStream());
		} catch (IOException e) {

			logger.severe(e, e);
			throw new SienaException(e);
		} finally {

			try {

				this.connector.close();
			} catch (IOException e) {

				logger.severe(e, e);
				throw new SienaException(e);
			}
		}

		return response;
	} // send.

	protected Object sendMessageWithoutBody(Object request,
			Serializer serializerIN, Serializer serializerOUT,
			RequestMethod method, boolean useMethodTunneling) {

		Object response = null;
		ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();

		try {

			serializerIN.serialize(request, arrayOutputStream);

			this.connector.connect(method, useMethodTunneling,
					arrayOutputStream.toString().trim());

			response = serializerOUT.deserialize(this.connector
					.getInputStream());
		} catch (IOException e) {

			logger.severe(e, e);
			throw new SienaException(e);
		} finally {

			try {

				this.connector.close();
			} catch (IOException e) {

				logger.severe(e, e);
				throw new SienaException(e);
			}
		}

		return response;
	} // sendMessageWithoutBody.

	/**
	 * Send the request by post. The format of the message depends of the input
	 * serializer that was set in the class.
	 * 
	 * @param request
	 *            Object to send
	 * @return Object response
	 */
	protected Object sendPost(Object request) {

		return this.sendPost(request, this.serializerInput,
				this.serializerOutput);
	} // sendPost.

	/**
	 * Send the request by post. The format of the message depends of the input
	 * serializer that was set in the class.
	 * 
	 * @param request
	 *            Object to send
	 * @param serializerIN
	 *            {@link Serializer} serializer for the input.
	 * @param serializerOUT
	 *            {@link Serializer} serializer for the output.
	 * @return Object response
	 */
	protected Object sendPost(Object request, Serializer serializerIN,
			Serializer serializerOUT) {

		return this.sendMessageWithBody(request, serializerIN, serializerOUT,
				RequestMethod.POST, this.isUseMethodTunnelingByDefault());
	} // sendPost.

	protected Object sendPut(Object request) {

		return this.sendPut(request, this.serializerInput,
				this.serializerOutput);
	} // sendPost.

	/**
	 * Send the request by post. The format of the message depends of the input
	 * serializer that was set in the class.
	 * 
	 * @param request
	 *            Object to send
	 * @param serializerIN
	 *            {@link Serializer} serializer for the input.
	 * @param serializerOUT
	 *            {@link Serializer} serializer for the output.
	 * @return Object response
	 */
	protected Object sendPut(Object request, Serializer serializerIN,
			Serializer serializerOUT) {

		return this.sendMessageWithBody(request, serializerIN, serializerOUT,
				RequestMethod.POST, this.isUseMethodTunnelingByDefault());
	} // sendPost.

	protected Object sendGet(Object request) {

		return this.sendGet(request, this.serializerInput,
				this.serializerOutput);
	} // sendGet.

	protected Object sendGet(Object request, Serializer serializerIN,
			Serializer serializerOUT) {

		return this.sendMessageWithoutBody(request, serializerIN, serializerOUT,
				RequestMethod.GET, this.isUseMethodTunnelingByDefault());
	} // sendGet.
	

	protected Object sendDelete(Object request) {

		return this.sendDelete(request, this.serializerInput,
				this.serializerOutput);
	} // sendGet.

	protected Object sendDelete(Object request, Serializer serializerIN,
			Serializer serializerOUT) {

		return this.sendMessageWithoutBody(request, serializerIN, serializerOUT,
				RequestMethod.DELETE, this.isUseMethodTunnelingByDefault());
	} // sendGet.
	


	public boolean isUseMethodTunnelingByDefault() {
		return useMethodTunnelingByDefault;
	}

	public void setUseMethodTunnelingByDefault(
			boolean useMethodTunnelingByDefault) {
		this.useMethodTunnelingByDefault = useMethodTunnelingByDefault;
	}

} // E:O:F:AbstractRemotePersistenceManager.